/**
 * @file uffs_api.c
 * @brief Maps the HAL of esp_partition <-> uffs
 * @author Brian Pugh
 */

#define ESP_LOCAL_LOG_LEVEL ESP_LOG_INFO

#include "esp_log.h"
#include "esp_vfs.h"
#include "uffs_api.h"
#include "spi_w25n01gvzeig.h"


#if 1
//bolck num, page num --> page addr
#define BLOCK_PAGE_NUM_to_PAGE_ADDR_H(BLOCK_NUM,PAGE_NUM)   (uint8_t)(((BLOCK_NUM) >> 2) & 0xFF)
#define BLOCK_PAGE_NUM_to_PAGE_ADDR_L(BLOCK_NUM,PAGE_NUM)   (uint8_t)((((BLOCK_NUM) & 0x3) << 6)|(PAGE_NUM))


/******************************************************************************
* Function:    nand_init_flash
* Description:nand_init_flash
* Input:       
* Output:      null
* Return:      
******************************************************************************/
static int nand_init_flash(uffs_Device *dev)
{  
    uint32_t JEDEC_ID = 0;
    nand_Reset();
    nand_unprotect();
    nand_write_enable();
    JEDEC_ID = nand_read_JEDEC_ID();
    if(JEDEC_ID != 0xEFAA21)
    {
        printf("nand error: bad id, 0x%x\r\n",JEDEC_ID);
    	return -1;
    }
    printf(LOG_COLOR_I"[W25N01GVZEIG]:JEDEC ID 0x%x\r\n",JEDEC_ID);
    return 0;
}

/******************************************************************************
* Function:    nand_release_flash
* Description:nand_release_flash
* Input:       
* Output:      null
* Return:      
******************************************************************************/
static int nand_release_flash(uffs_Device *dev)
{
    //nand_deinit_ssp();
	return 0;
}
/******************************************************************************
* Function:    nand_read_page_with_layout
* Description:for hw ecc, regardless of ecc, ecc_store
* Input:       
* Output:      null
* Return:      
******************************************************************************/
 
static int nand_read_page_with_layout(uffs_Device *dev, u32 block, u32 page, 
               u8* data, int data_len, u8 *ecc,uffs_TagStore *ts, u8 *ecc_store)
{
    int ret = UFFS_FLASH_NO_ERR;
    
    uint8_t val = 0;	
    uint8_t ecc_flag = 0;

    uint8_t spare[64] = {0};

    uint8_t page_addrh = BLOCK_PAGE_NUM_to_PAGE_ADDR_H((block),(page));
    uint8_t page_addrl = BLOCK_PAGE_NUM_to_PAGE_ADDR_L((block),(page));

    nand_page_data_read(page_addrh, page_addrl);//read data to the nand buffer
    ecc_flag = nand_check_embedded_ECC_flag();
    if(ecc_flag ==0)
        ret = UFFS_FLASH_NO_ERR;
    else if(ecc_flag == 1)
       ret = UFFS_FLASH_ECC_OK;
    else
       ret = UFFS_FLASH_ECC_FAIL;

    if(ret != UFFS_FLASH_NO_ERR)
        return ret;
    
    if (data == NULL && ts == NULL)//read bad block mark
    {
        nand_normal_read(0x08, 0x00, &val, 1);//spare area start addr = 0x800        
		ret = (val == 0xFF ? UFFS_FLASH_NO_ERR : UFFS_FLASH_BAD_BLK);

        return ret;
	}

    if(data !=NULL && data_len > 0)//read page data
    {
        nand_continuous_normal_read(data, data_len);           
    }

    if (ts != NULL)//read spare data,construct ts 
    {
        memset(spare, 0xFF, 64);
        memset(ts, 0, sizeof(uffs_TagStore));
        nand_normal_read(0x08, 0x00, spare, 64);//spare area start addr = 0x800

        ts->dirty = (spare[0x04]>>7) & 0x01;
        ts->valid = (spare[0x04]>>6) & 0x01;
        ts->type = (spare[0x04]>>4) & 0x03;        
        ts->block_ts = (spare[0x04]>>2) & 0x03;
        
        ts->data_len = ((spare[0x04] & 0x3)<<10) | spare[0x05] << 2 | ((spare[0x06] >> 6) & 0x03);        
        ts->serial =  ((spare[0x06] & 0x3F)<<8) | spare[0x07];
        
        ts->parent = (spare[0x14]<<2) | ((spare[0x15] >> 6) & 0x03);
        ts->page_id = spare[0x15] & 0x3F;        
        ts->reserved = ((spare[0x16] >> 4) & 0x0F);        
        ts->tag_ecc =  ((spare[0x16] & 0x0F)<<8) | spare[0x17];

        if(spare[0x37] == 0xFF)//seal byte
        {
            ret = UFFS_FLASH_NOT_SEALED;
        }
	}
    
	return ret;
}

/******************************************************************************
* Function:    nand_write_page_with_layout
* Description:for hw ecc auto, regardless ecc, ecc will be auto writed by hw
* Input:       
* Output:      null
* Return:      
******************************************************************************/
 
static int nand_write_page_with_layout(uffs_Device *dev, u32 block, u32 page, 
          const u8 *data, int data_len, const u8 *ecc, const uffs_TagStore *ts)
{
    int ret = UFFS_FLASH_NO_ERR;
	uint8_t val = 0;
    uint8_t ts_array[8] = {0};
  
    uint8_t page_addrh = BLOCK_PAGE_NUM_to_PAGE_ADDR_H((block),(page));
    uint8_t page_addrl = BLOCK_PAGE_NUM_to_PAGE_ADDR_L((block),(page));

    nand_unprotect();
    nand_write_enable();

    if(data && data_len > 0)//write page data 
    {
        nand_page_program_random(0, 0,data,data_len);
    }

    if(ts)
    {        
        ts_array[0] = (ts->dirty & 0x1)<< 7 | 
                      (ts->valid & 0x1)<< 6 | 
                      (ts->type  & 0x3 )<< 4 | 
                      (ts->block_ts & 0x3) << 2 |
                      (ts->data_len & 0xFFF) >>10;//the high 2 bits                             
        ts_array[1] = (ts->data_len >> 2) & 0xFF;         
        ts_array[2] = (ts->data_len & 0x03) <<6 |
                      ((ts->serial >> 8) & 0x3F);                      
        ts_array[3] = (ts->serial & 0xFF);
        ts_array[4] = (ts->parent >> 2) & 0xFF;
        ts_array[5] = (ts->parent & 0x03) << 6 |
                      (ts->page_id & 0x3F);        
        ts_array[6] = (ts->reserved & 0xF) << 4 |
                      ((ts->tag_ecc >> 8) & 0x0F);        
        ts_array[7] = (ts->tag_ecc & 0xFF);
          
        nand_page_program_random(0x08, 0x04, &ts_array[0], 4);//data sheet: move ts from User Data II to User Data I           
        nand_page_program_random(0x08, 0x14, &ts_array[4], 4);       
        
        nand_page_program_random(0x08, 0x37, &val, 1);//seal byte,placed at 0x837
     }

     if(data == NULL && ts == NULL)//mark bad block
     {
        nand_page_program_random(0x08, 0x00, &val, 1);
        ret = UFFS_FLASH_BAD_BLK; 
     }
    
     nand_program_excute(page_addrh, page_addrl);//excute write
     if(nand_check_program_fail_flag())
        ret = UFFS_FLASH_IO_ERR;//program fail

	 return ret;
}

/******************************************************************************
* Function:    nand_erase_block
* Description:nand_erase_block
* Input:       
* Output:      null
* Return:      
******************************************************************************/
static int nand_erase_block(uffs_Device *dev, u32 block)
{
    int ret = 0;
    
    uint8_t page_addrh = BLOCK_PAGE_NUM_to_PAGE_ADDR_H((block),(0));
    uint8_t page_addrl = BLOCK_PAGE_NUM_to_PAGE_ADDR_L((block),(0));
    
    ret = nand_erase_a_block(page_addrh, page_addrl);
    if(ret != 0)
        printf("nand erase block fail: %u", block);
    
    return ret;        
}

uffs_FlashOps nand_w25m01gvzeig_ops = 
{
	nand_init_flash,	              // InitFlash()
	nand_release_flash,	              // ReleaseFlash()
    NULL,       		              // ReadPage()
	nand_read_page_with_layout,	      // ReadPageWithLayout
    NULL,           	              // WritePage()
	nand_write_page_with_layout,      // WritePageWithLayout
	NULL,                             // IsBadBlock(), let UFFS take care of it.
	NULL,            		          // MarkBadBlock(), let UFFS take care of it.
	nand_erase_block,	              // EraseBlock()
	NULL,                             // CheckErasedBlock
};

#endif